package com.cy.pj.sys.service.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.exception.ServiceException;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.common.util.ShiroUtils;
import com.cy.pj.sys.dao.SysUserDao;
import com.cy.pj.sys.dao.SysUserRoleDao;
import com.cy.pj.sys.pojo.SysUser;
import com.cy.pj.sys.pojo.SysUserDept;
import com.cy.pj.sys.service.SysUserService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;

/**
 * 	在业务层对象的类上添加了@Transactional注解,表示类中所有方法都要进行事务控制:
 * 1)开启事务
 * 2)执行目标方法
 * 3)提交或回滚事务
 * 	FAQ?
 * 1)事务属性timeout的含义是什么?是否允许事务超时,默认不允许(-1),一直等.
 * 2)事务属性rollbackFor表示什么?出现什么异常事务要回滚
 * 3)事务属性readOnly含义是什么?这里的事务是否为只读事务.(只读事务中不允许执行更新操作)
 * 4)事务属性isolation含义是什么?用于设置隔离级别I(多个事务并发执行时可能会出现什么问题?-脏读,不可重复读,幻读)
 * 
 */
@Transactional(timeout =60,rollbackFor =  Throwable.class,readOnly = false
              ,isolation = Isolation.READ_COMMITTED,
               propagation = Propagation.REQUIRED)
@Service
public class SysUserServiceImpl implements SysUserService {

	//has a 
	@Autowired
	private SysUserDao sysUserDao;
	
	@Autowired
	private SysUserRoleDao sysUserRoleDao;
	
	
	
	@Override
	public int updatePassword(String sourcePassword, 
			String newPassword, String cfgPassword) {
		//1.参数校验
		if(StringUtils.isEmpty(sourcePassword))
			throw new IllegalArgumentException("原密码不能为空");
		if(StringUtils.isEmpty(newPassword))
			throw new IllegalArgumentException("新密码不能为空");
		if(!newPassword.equals(cfgPassword))
			throw new IllegalArgumentException("两次输入的新密码必须一致");
		//验证原密码是否正确.
		SysUser user=ShiroUtils.getUser();
		String salt=user.getSalt();
		SimpleHash sh=new SimpleHash("MD5", sourcePassword, salt, 1);
		if(!user.getPassword().equals(sh.toHex()))
			throw new IllegalArgumentException("输入的原密码不正确");
		//2.修改密码
		String newSalt=UUID.randomUUID().toString();
		String newHashedPassword=
		new SimpleHash("MD5", newPassword, newSalt, 1).toHex();
		int rows=sysUserDao.updatePassword(user.getUsername(), newHashedPassword, newSalt);
		//3.校验结果并返回
		if(rows==0)
			throw new ServiceException("此用户可能已经不存在");//几率很小
		return rows;
	}
	
	@Transactional(readOnly = true)//查询操作事务属性readOnly的值建议为true,因为这样设置查询性能会比较高.
	@Override
	public Map<String, Object> findById(Integer id) {
		//1.参数校验
		//2.基于用户id查询用户以及用户对应部门信息
		SysUserDept user=sysUserDao.findById(id);//数据层做了两个表的关联查询(用户表和部门表)
		if(user==null)
			throw new ServiceException("用户可能已经不存在了");
		//3.基于用户id查询用户对应的角色信息
		List<Integer> roleIds=sysUserRoleDao.findRoleIdsByUserId(id);
		//4.将查询结果封装到map并返回
		Map<String,Object> map=new HashMap<>();
		map.put("user", user);
		map.put("roleIds", roleIds);
		return map;
	}
	@RequiredLog(operation = "修改用户")
	@Override
	public int updateObject(SysUser entity, Integer[] roleIds) {
		//1.参数校验
		//..........
		//2.更新用户自身信息
		int rows=sysUserDao.updateObject(entity);
		//3.更新用户对应的角色关系数据
		//3.1删除原有关系数据
		sysUserRoleDao.deleteObjectsByUserId(entity.getId());
		//3.2添加新的关系数据
		sysUserRoleDao.insertObjects(entity.getId(), roleIds);
		//4.返回保存结果
		return rows;
	}
	/**
	 * 1.@RequiredLog 注解描述的方法为一个日志切入点方法
	 * 2.@Transactional 注解描述的方法为一个事务切入点方法,这个方法在执行时:
	 *   1)目标方法运行之前会开启事务
	 *   2)目标方法运行结束会提交或回滚事务,
	 *   FAQ? 
	     1)请问事务控制代码你写了吗?没写
	     2)假如事务控制代码让你写,你会写到哪里?切面对象方法中(这里的方法一般为通知方法).
	 */
	@Transactional(noRollbackFor = ServiceException.class)
	@RequiredLog(operation = "保存用户")
	@Override
	public int saveObject(SysUser entity, Integer[] roleIds) {
		//1.参数校验
		//..........
		//2.保存用户自身信息
		//2.1构建一个盐值对象
		String salt=UUID.randomUUID().toString();//随机字符串
		//2.2对密码进行盐值加密(加密算法MD5-特点:不可逆,相同内容加密结果也相同)
		//2.2.1加密方式1(借助spring框架工具类DigestUtils)
		//String newPassword=DigestUtils.md5DigestAsHex((salt+entity.getPassword()).getBytes());
		//2.2.2加密方式2(借助shiro框架中的API)
		SimpleHash sh=new SimpleHash("MD5", entity.getPassword(), salt, 1);
		String newPassword=sh.toHex();
		System.out.println("newPassword="+newPassword);
		entity.setSalt(salt);
		entity.setPassword(newPassword);
		//2.3将entity对象持久化到数据库
		int rows=sysUserDao.insertObject(entity);
		//3.保存用户对应的角色关系数据
		rows=sysUserRoleDao.insertObjects(entity.getId(), roleIds);
		//4.返回保存结果
		return rows;
	}
	/**
	 * @RequiresPermissions 注解描述的方法为一个权限控制切入点方法(目标方法),当我们访问此方法时需要授权才能访问?
	 * FAQ:
	 * 1)什么情况下会进行授权?(当用户权限中包含@RequiresPermissions注解内部定义的字符串)
	 * 2)如何获取登录用户的权限标识?(基于登陆用户信息访问用户角色,角色菜单,菜单表中的数据)
	 * 3)由谁授权?(SecurityManager)
	 * 4)在什么样的方法中进行授权?(AOP编程过程中的通知方法中.)
	 * 5)假如用户没有访问此方法的权限怎么办?抛出异常
	 */
	@RequiresPermissions("sys:user:update")//"sys:user:update"为一个权限标识
	@RequiredLog(operation = "禁用启用")
	@Override
	public int validById(Integer id, Integer valid) {
		//1.参数校验
		//2.修改状态
		int rows=sysUserDao.validById(id, valid, "admin");//这里的admin是将来的登陆用户,现在数据为假数据
		//3.验证结果
		if(rows==0)
			throw new ServiceException("记录可能已经不存在");
		return rows;
	}
	
	@Transactional(readOnly = true)
	@RequiredLog(operation = "日志分页查询")
	@Override
	public PageObject<SysUserDept> findPageObjects(String username, Integer pageCurrent) {        
		System.out.println("SysUserServiceImpl.findPageObjects.thread->"+Thread.currentThread().getName());
		//1.参数校验
		if(pageCurrent==null||pageCurrent<1)
			throw new IllegalArgumentException("页码值不正确");
		//2.查询当前页记录
		//使用PageHelper最关键的一个步骤,这里表示启动PageHelper的内置拦截器,拦截查询请求,然后对SQL做处理.
		Page<SysUserDept> page=PageHelper.startPage(pageCurrent, 3);//这里的3为pagesize
		List<SysUserDept> records=sysUserDao.findPageObjects(username);
		//4.封装查询结果并返回
	   return new PageObject<>(page.getTotal(), records, Long.valueOf(page.getPages()), 3, pageCurrent.longValue());
	}

}
